package jmine.tec.rpc.server.representation.representers.reflection;

import static jmine.tec.rpc.RPCMessages.REFLECTION_REPRESENTATION_FAILED;

import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.rpc.RPCMessages;
import jmine.tec.rpc.common.RPCException;
import jmine.tec.rpc.server.representation.RepresentationContext;
import jmine.tec.rpc.server.representation.representers.AbstractRepresenter;

import org.apache.commons.beanutils.PropertyUtils;

/**
 * Representer que atua por reflexão.
 * 
 * @author lundberg
 * @param <R> tipo de representação
 * @param <E> tipo de entidade representada
 */
public class ReflectionRepresenter<R, E> extends AbstractRepresenter<R, E> {

    private Map<String, PropertyRepresenter<R, E>> representers = new ConcurrentHashMap<String, PropertyRepresenter<R, E>>();

    /**
     * Construtor
     * 
     * @param representationClass representationClass
     * @param representedClass representedClass
     */
    public ReflectionRepresenter(Class<R> representationClass, Class<E> representedClass) {
        super(representationClass, representedClass);
    }

    /**
     * {@inheritDoc}
     */
    public R represent(RepresentationContext context, E object) {
        try {
            return this.doRepresent(context, object);
        } catch (Exception e) {
            throw new RPCException(REFLECTION_REPRESENTATION_FAILED.create(this.getRepresentedClass().getCanonicalName(), this
                    .getRepresentationClass().getCanonicalName(), e.getMessage()), e);
        }
    }

    /**
     * Cria a representação
     * 
     * @param context context
     * @param object object
     * @return representation
     * @throws Exception Exception
     */
    private R doRepresent(RepresentationContext context, E object) throws Exception {
        R representation = this.getRepresentationClass().getConstructor(new Class[0]).newInstance();
        context.register(object, representation);
        for (PropertyDescriptor representationProperty : PropertyUtils.getPropertyDescriptors(this.getRepresentationClass())) {
            this.findPropertyRepresenter(representationProperty).representProperty(context, representation, representationProperty, object);
        }
        return representation;
    }

    /**
     * Busca o representador adequado à propriedade
     * 
     * @param representationProperty representationProperty
     * @return PropertyRepresenter
     */
    private PropertyRepresenter<R, E> findPropertyRepresenter(PropertyDescriptor representationProperty) {
        if (this.representers.containsKey(this.propertyKey(representationProperty))) {
            return this.representers.get(this.propertyKey(representationProperty));
        } else {
            throw new RPCException(RPCMessages.NO_PROPERTY_REPRESENTER_FOUND.create(representationProperty.getName()));
        }
    }

    /**
     * Encontra um representador para a propriedade
     * 
     * @param defaultRepresenters representers
     * @param representationProperty representationProperty
     * @return PropertyRepresenter
     */
    private PropertyRepresenter<R, E> findPropertyRepresenter(List<? extends PropertyRepresenter<R, E>> defaultRepresenters,
            PropertyDescriptor representationProperty) {
        for (PropertyRepresenter<R, E> representer : defaultRepresenters) {
            if (representer.isApplicableTo(representationProperty)) {
                return representer;
            }
        }
        return null;
    }

    /**
     * Adiciona representadores específicos por propriedade
     * 
     * @param specific specific
     */
    public void setSpecificRepresenters(Map<String, ? extends PropertyRepresenter<R, E>> specific) {
        this.representers.putAll(specific);
    }

    /**
     * @param defaultRepresenters the representers to set
     */
    public void setDefaultRepresenters(List<? extends PropertyRepresenter<R, E>> defaultRepresenters) {
        for (PropertyDescriptor property : PropertyUtils.getPropertyDescriptors(this.getRepresentationClass())) {
            if (!this.representers.containsKey(this.propertyKey(property))) {
                PropertyRepresenter<R, E> representer = this.findPropertyRepresenter(defaultRepresenters, property);
                if (representer != null) {
                    this.representers.put(this.propertyKey(property), representer);
                }
            }
        }
    }

    /**
     * @param representationProperty representationProperty
     * @return key
     */
    protected String propertyKey(PropertyDescriptor representationProperty) {
        return representationProperty.getName();
    }
}
